package org.example.interceptor;

import org.example.domain.Ticket;
import org.example.domain.User;
import org.example.service.imp.TicketServiceImp;
import org.example.service.imp.UserServiceImp;
import org.example.util.CommonParam;
import org.example.util.CookieUtil;
import org.example.util.UserThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Date;

/**
 * 这个拦截器用来拦截一个登录凭证 存储在cookie中 key为ticket
 * 如果存在 并且没有过期  获取用户信息 这次请求可能会需要 不存在session中  分布式部署的时候  负载均衡会导致多个服务器存在当前用户的session 不好处理(这些我不是很能理解)
 * 老师将用户信息存储到ThreadLocal中  这笔请求一个线程处理  当前线程内存储用户信息
 * 这里就有个问题 一个用户多次访问项目不同功能 都会判断 获取 存储  性能不好 可以存储到内存服务器中redis  后面再说
 */
@Component
public class LoginTicketInterceptor implements HandlerInterceptor {
    @Autowired
    private TicketServiceImp ticketServiceImp;
    @Autowired
    private UserServiceImp userServiceImp;
    @Autowired
    private UserThreadLocalUtil userThreadLocalUtil;
    @Autowired
    private CookieUtil cookieUtil;
    @Value("${server.servlet.context-path}")
    private String path;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //判断用户是否登录过  cookie是否存在key = ticket的cookie
        Cookie[] cookies = request.getCookies();
        if (cookies == null || cookies.length == 0){
            return true;
        }
        for (Cookie cookie : cookies){
            if (cookie.getName().equals(CommonParam.TICKET_SYMBOL)){
                //存在  查询凭证
                Ticket ticket = ticketServiceImp.searchByTicket(cookie.getValue());
                //判断是否过期 status 和 expired
                if (ticket == null || ticket.getStatus() == 0 || ticket.getUserId()<=0 || ticket.getExpired().before(new Date())){
                    //能走到这里 说明cookie中存在ticket 但是ticket过期了(出现异常) 需要更改ticket 退出
                    //还要往后面走吗  有些请求获取了cookie中的ticket  但是这时候user没有  可能导致页面显示问题
                    //我们这里这样做  先退出(删除ticket  然后去登录页)
                    cookieUtil.deleteTicket(cookie.getValue(),response);
                    response.sendRedirect(path+"/user/login.html");
                    return false;
                }
                //没有过期  获取user信息
                User user = userServiceImp.searchById(ticket.getUserId());
                //不存储session中 存储到ThreadLocal中
                userThreadLocalUtil.set(user);
                request.setAttribute(CommonParam.TICKET_SYMBOL,CommonParam.VERIFY_FINISH);
                //用了security 需要将用户存储SecurityContextHolder
                SecurityContext context = SecurityContextHolder.getContext();
                //因为用的是账号密码登录用特定的token
                Authentication authentication = new UsernamePasswordAuthenticationToken(user,user.getPassword(),user.getAuthorities());
                context.setAuthentication(authentication);
                return true;
            }
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        if (request.getAttribute(CommonParam.TICKET_SYMBOL)!=null && modelAndView!=null){
            //视图模板处理之前 将user信息存储
            modelAndView.addObject(CommonParam.LOGIN_USER_SYMBOL,userThreadLocalUtil.get());
        }

     }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //将threadLocal中数据删除 好像只能存储一个值 额  这个方法是controller响应后调用的 try catch finally
        userThreadLocalUtil.remove();
        //需要将security中的authentication删除
        SecurityContextHolder.clearContext();
    }
}
